// 实现数据拦截
function defineReactive(obj, key, val) {
    // 递归解决深层级对象问题
    observe(val);

    // 依赖收集的管理者
    const deps = new Dep();

    Object.defineProperty(obj, key, {
        get() {
            Dep.target && deps.addDep(Dep.target);
            return val;
        },
        set(v) {
            if (val !== v) {
                // 当新值是对象的时候，我们依然要对其做响应式处理
                observe(v);
                val = v;
                // 值改变通知修改
                deps.notify();
            }
        }
    })
}

function observe(obj) {
    if (typeof obj === "object" && obj !== null) {
        Object.keys(obj).forEach(key => defineReactive(obj, key, obj[key]))
    }
}

/**
 * 动态新增响应式对象
 * @param obj
 * @param key
 * @param val
 */
function set(obj, key, val) {
    defineReactive(obj, key, val);
}

function proxyData(vm) {
    Object.keys(vm.$data).forEach((key) => {
        Object.defineProperty(vm, key, {
            get() {
                return vm.$data[key];
            },
            set(v) {
                vm.$data[key] = v;
            }
        })
    })
}

class KVue {
    constructor(options) {
        // 1.接收选项数据
        this.$options = options;
        let data = options.data;
        if (typeof options.data === "function") {
            data = options.data();
        }
        this.$data = data;

        // 2.将data数据响应式处理
        observe(this.$data);

        // 3.对data数据进行代理，使其使用KVue实例也可以访问
        proxyData(this);

        // 4.编译模板
        new Compile(options.el, this);
    }
}


class Compile {
    constructor(el, vm) {
        // 获取dom元素
        const dom = document.querySelector(el);
        this.$vm = vm;
        // 解析模板
        this.compile(dom);
    }

    compile(dom) {
        // 这里有两个属性可以获取到子节点，childNodes和children,但是children只能获取到节点，并不可以获取到文本节点
        let childNodes = dom.childNodes;
        childNodes.forEach(node => {
            if (this.isElement(node)) {
                // 解析属性：动态绑定，指令，事件
                const attrs = node.attributes;
                Array.from(attrs).forEach(attr => {
                    const attrName = attr.name;
                    const exp = attr.value;
                    // 解析k-指令
                    if (this.isDir(attrName)) {
                        let fnName = attrName.substring(2);
                        this[fnName] && this[fnName](node, exp);
                    }

                })
                if (node.childNodes.length > 0) {
                    // 递归获取内部内容
                    this.compile(node);
                }
            } else if (this.isInter(node)) {
                this.compileText(node);
            }
        })
    }

    /**
     * 高阶函数 - 修改专用户，处理所有动态绑定，并且完成Watcher收集
     * @param node 节点
     * @param key  需要修改的key
     * @param type 类型，text，html等指令类型
     */
    update(node, key, type){
        // 执行dom操作，初始化当前页面
        const fn = this[type + "Updater"];
        fn && fn(node, this.$vm[key]);
        // 执行依赖收集操作
        new Watcher(this.$vm, key, function (val) {
            fn && fn(node, val);
        })
    }

    // dom操作专用
    textUpdater(node, value){
        node.textContent = value;
    }

    // dom操作专用
    htmlUpdater(node, value){
        node.innerHTML = value;
    }

    // 解析文本属性
    text(node, key) {
        this.update(node, key, "text");
    }

    // 解析html属性
    html(node, key) {
        this.update(node, key, "html");
    }

    // 插值表达式解析{{}}
    compileText(node) {
        this.update(node, RegExp.$1, "text");
    }

    isElement(node) {
        return node.nodeType === 1
    }

    isDir(attrName) {
        return attrName.startsWith("k-")
    }

    isInter(node) {
        return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent)
    }
}


class Watcher {
    constructor(vm, key, updater) {
        this.$vm = vm;
        this.key = key;
        this.updater = updater;

        // 实例化后就将其添加到Dep里去
        Dep.target = this;
        this.$vm[key];   // 触发get方法，实现依赖收集
        Dep.target = null;
    }

    // 这个方法是给Dep调用，作为更新函数使用
    update(){
        const newVal = this.$vm[this.key];
        this.updater.call(this.$vm, newVal);
    }
}

class Dep {
    constructor() {
        this.deps = []
    }

    // 这里的dep其实就是watcher
    addDep(dep){
        this.deps.push(dep)
    }

    notify(){
        this.deps.forEach(dep => {
            dep.update();
        })
    }
}
